﻿using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using Microsoft.Practices.Unity;
using Microsoft.Practices.Unity.Configuration;
using Microsoft.Practices.Unity.InterceptionExtension;
using WDA.Core.DynamicReconfiguration;
using WDA.Config;
using System.ServiceModel;

namespace WDA.Core
{
	/// <summary>
	/// WDAB runtime module for accessing remotly hosted services.
	/// </summary>
	internal class RemoteServicesModule : IModule
	{
		#region Fields
      private readonly bool _singleThreaded;
		private IUnityContainer _localContainer;
		private IUnityContainer _outerContainer;
		private IUnityContainer _stubContainer;		
		private bool _disposed;

		private string _configName;
		private string _hostName;	   

		private static readonly Logger _logger = new Logger(typeof(RemoteServicesModule));
		#endregion

      /// <summary>
      /// Creates new module instance.
      /// </summary>
      public RemoteServicesModule()
      {
      }

      /// <summary>
      /// Creates new module instance.
      /// </summary>
      /// <param name="singleThreaded">Does module run in single-threaded environment (will not be shared by
      /// many threads). Typicaly for Web server environment with on runtime instance per request.</param>
      public RemoteServicesModule(bool singleThreaded)
      {
         _singleThreaded = singleThreaded;
      }

		#region IModule Members
		public void Init(IUnityContainer outerContainer, IUnityContainer localContainer, IUnityContainer persistentContainer, string configName, string hostName)
		{
			_logger.WriteInfo("Initializing Remote Services Module...");
			_outerContainer = outerContainer;
			_localContainer = localContainer;

			_configName = configName;
			_hostName = hostName;

			_stubContainer = new UnityContainer();
			_stubContainer.RegisterInstance(WDAContainerNames.InternalContainerName, _localContainer);
			_stubContainer.RegisterInstance(WDAContainerNames.PersistentInternalContainerName, persistentContainer);
			_stubContainer.RegisterInstance(_stubContainer);
         _stubContainer.AddNewExtension<Interception>();		   
			_localContainer.RegisterInstance(WDAContainerNames.StubContainerName, _stubContainer);


		   RuntimeVersion ver = persistentContainer.Resolve<RuntimeVersion>();

         ConfigurationLockProxyInterceptor configLockHandler = new ConfigurationLockProxyInterceptor(ver.VersionNumber);
         configLockHandler = persistentContainer.BuildUp(configLockHandler);

         _stubContainer.Configure<Interception>().AddPolicy("ConfigLock").AddMatchingRule(new MatchAllRule())
            .AddCallHandler(configLockHandler);

         IEnumerable<IModuleConfigurator> moduleConfig = _outerContainer.ResolveAll<IModuleConfigurator>();
         foreach (IModuleConfigurator cfg in moduleConfig)
         {
            if (cfg.ShouldApply(WDAContainerNames.StubContainerName))
            {
               cfg.Apply(_stubContainer);
            }
         }

			_logger.WriteInfo("Remote Services Module initialized.");
		}

		public void CreateServiceControllers(IConfiguration config, Dictionary<Type, IServiceController> serviceControllers)
		{
			IRemoteServiceControllerFactory ctrlFactory = _outerContainer.Resolve<IRemoteServiceControllerFactory>();
			IBindingFactory bindingFactory = _outerContainer.Resolve<IBindingFactory>();

			Dictionary<Type, List<IService>> servicesByType = new Dictionary<Type, List<IService>>();

			foreach (IHost host in config.Hosts)
			{
				if (host.Name != _hostName)
				{					
					foreach (IService svc in host.Services)
					{
						Type serviceType = Type.GetType(svc.InterfaceName, true);
						List<IService> services;
						if (servicesByType.TryGetValue(serviceType, out services))
						{
							services.Add(svc);
						}
						else
						{
							services = new List<IService>();
							services.Add(svc);
							servicesByType[serviceType] = services;
						}
					   _stubContainer.Configure<Interception>().SetDefaultInterceptorFor(serviceType, new InterfaceInterceptor2());

                  _stubContainer.Configure<Interception>()
                     .AddPolicy(Guid.NewGuid().ToString()).AddMatchingRule(new TypeMatchingRule(serviceType))
                     .AddCallHandler(new InterceptionContextSetterCallHandler(svc.Name, serviceType, null));					   
					}
				}
			}
			foreach (Type serviceType in servicesByType.Keys)
			{
				List<IService> services = servicesByType[serviceType];
				Type serviceMonitorType = Type.GetType(services[0].MonitorInterfaceName, true);
				Type implementationType = Type.GetType(services[0].ImplementationName, true);
				string serviceName = services[0].Name;
				if (!services.All(svc => svc.MonitorInterfaceName == services[0].MonitorInterfaceName
					&& svc.ImplementationName == services[0].ImplementationName))
				{
					throw new InvalidOperationException("All service endpoints should have same monitor type and implementation.");
				}

				List<RemoteServiceDescription> descriptions = new List<RemoteServiceDescription>();
				foreach (IService svc in services)
				{
					BuildServiceDescription(serviceType, svc, bindingFactory, descriptions);
				}

				RemoteServiceController ctrl = ctrlFactory.Create(serviceType, serviceMonitorType, _singleThreaded, descriptions);
				ctrl = (RemoteServiceController)_localContainer.BuildUp(ctrl.GetType(), ctrl);
				serviceControllers[serviceType] = ctrl;
			}
		}

	   private static void BuildServiceDescription(Type serviceType, IService svc, IBindingFactory bindingFactory, ICollection<RemoteServiceDescription> descriptions)
	   {
	      string baseServiceUri = svc.Host.BaseUri;
	      string baseMonitorUri = svc.Host.BaseMonitorUri;
	      if (svc.BasePort != null)
	      {
	         baseServiceUri = baseServiceUri.Replace("#PORT#", svc.BasePort.ToString());
	         baseMonitorUri = baseMonitorUri.Replace("#PORT#", (svc.BasePort + 1).ToString());
	      }

	      string remoteServiceAddress;
	      string remoteServiceMonitorAddress;
	      if (!string.IsNullOrEmpty(svc.Uri))
	      {
	         remoteServiceAddress = svc.Uri;
	      }
	      else
	      {
	         remoteServiceAddress = string.Format("{0}/{1}", baseServiceUri, svc.Name);
	      }
	      if (!string.IsNullOrEmpty(svc.MonitorUri))
	      {
	         remoteServiceMonitorAddress = svc.MonitorUri;
	      }
	      else
	      {
	         remoteServiceMonitorAddress = string.Format("{0}/{1}", baseMonitorUri, svc.Name);
	      }

	      RemoteServiceDescription descr = new RemoteServiceDescription
	                                          {
	                                             MonitorAddress = new EndpointAddress(remoteServiceMonitorAddress),
	                                             MonitorBinding = bindingFactory.CreateBinding(),
	                                             ServiceAddress = new EndpointAddress(remoteServiceAddress),
	                                             ServiceBinding = bindingFactory.CreateBinding()
	                                          };
	      _logger.WriteInfo("Setting up connection to service {0} located at {1}.", serviceType.FullName, remoteServiceAddress);
	      descriptions.Add(descr);
	   }

	   #endregion

		#region IDisposable Members
		public void Dispose()
		{
			Dispose(true);
		}
		public virtual void Dispose(bool disposing)
		{
			if (!_disposed)
			{
				if (disposing)
				{					
					_disposed = true;
				}
			}
			GC.SuppressFinalize(this);
		}
		#endregion
	}
}
